/* SPF (Sender Policy Framework) implementation.

Translation to C Copyright (c) 2007 Roberto Alsina <ralsina@kde.org>
Copyright (c) 2003, Terence Way
Portions Copyright (c) 2004,2005,2006 Stuart Gathman <stuart@bmsi.com>
Portions Copyright (c) 2005,2006 Scott Kitterman <scott@kitterman.com>
This module is free software, and you may redistribute it and/or modify
it under the same terms as Python itself, so long as this copyright message
and disclaimer are retained in their original form.

IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

For more information about SPF, a tool against email forgery, see
http://www.openspf.org/

For new versions check out http://code.google.com/p/raspf

*/

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "bstrlib/bstrlib.h"
#include "hash/hash.h"
#include "excc/exception-local.h"
#include "udns/udns.h"
#include "raspf.h"

void dump_hash( struct hashtable *hash)
{
    /* Iterator constructor only returns a valid iterator if
     * the hashtable is not empty */

    fprintf (stderr,"DUMPING HASH\n");
    void *itr;
    if (hashtable_count(hash) > 0)
    {
        bstring k,v;

        itr = hashtable_iterator(hash);
        do {
            k = hashtable_iterator_key(itr);
            v = hashtable_iterator_value(itr);
            fprintf (stderr,"%s - %s \n",k->data,v->data);

        } while (hashtable_iterator_advance(itr));
        free(itr);
    }
}

int __inited_dummy_dns=0;
struct hashtable *SPF;
struct hashtable *TXT;
struct hashtable *MX;
struct hashtable *A;
struct hashtable *AAAA;
struct hashtable *PTR;
struct hashtable *NONE;
struct hashtable *TO;

struct dns_ctx {};

void dummy_dns_free ()
{
    HASH_DESTROY(SPF);
    HASH_DESTROY(TXT);
    HASH_DESTROY(MX);
    HASH_DESTROY(A);
    HASH_DESTROY(AAAA);
    HASH_DESTROY(PTR);
    HASH_DESTROY(NONE);
    HASH_DESTROY(TO);
}

void dummy_dns_init(const char *zonefile)
{
    SPF  =  create_hashtable (16,hash_from_string,bstr_eq);
    TXT  =  create_hashtable (16,hash_from_string,bstr_eq);
    MX   =  create_hashtable (16,hash_from_string,bstr_eq);
    A    =  create_hashtable (16,hash_from_string,bstr_eq);
    AAAA =  create_hashtable (16,hash_from_string,bstr_eq);
    PTR  =  create_hashtable (16,hash_from_string,bstr_eq);
    NONE =  create_hashtable (16,hash_from_string,bstr_eq);
    TO   =  create_hashtable (16,hash_from_string,bstr_eq);

    FILE *f=NULL;

    if (!zonefile)
        zonefile=getenv("ZONEDATA");

    if (zonefile)
    {
        f= fopen (zonefile,"r");

        if (!f)
        {
            // Can't open file.
            fprintf (stderr,"Can't open zonedata file %s\n",zonefile);
            return;
        }
    }
    else
    {
        fprintf (stderr,"ZONEDATA is not set\n");
        return;
    }


    struct hashtable *HASH=0;
    bstring line;
    for (;;)
    {
        line = bgets ((bNgetc) fgetc, f, '\n');
        if (!line)
        {
            // End of stream
            break;
        }
        //You may lack a \n at the last line :-P
        if (line->slen > 0 && line->data[line->slen - 1] == '\n')
        {
            btrunc (line, line->slen - 1);
        }

        struct bstrList *l;
        l=bsplit(line,'|');

        if (biseq(l->entry[1],&_AAAA))
        {
            HASH=AAAA;
        }
        else if (biseq(l->entry[1],&_A))
        {
            HASH=A;
        }
        else if (biseq(l->entry[1],&_TO))
        {
            bstring s=hashtable_search(TO,l->entry[0]);
            if (!s)
            {
                HASH_INSERT(TO,l->entry[0]->data,"xxx");
                bstrListDestroy(l);
                bdestroy (line);
                continue;
            }

        }
        else if (biseq(l->entry[1],&_SPF))
        {
            HASH=SPF;
        }
        else if (biseq(l->entry[1],&_NONE))
        {
            HASH=NONE;
        }
        else if (biseq(l->entry[1],&_TXT))
        {
            HASH=TXT;
        }
        else if (biseq(l->entry[1],&_PTR))
        {
            HASH=PTR;
        }
        else if (biseq(l->entry[1],&_MX))
        {
            HASH=MX;
        }

        bstring ss=hashtable_search(HASH,l->entry[0]);
        if (ss)
        {
            bconchar(ss,'|');
            bconcat(ss,l->entry[2]);

        }
        else
        {
            HASH_INSERT(HASH,l->entry[0]->data,l->entry[2]->data);
        }
        // FOR case at-null
        if (line->data[line->slen-1]==0)
        {
            bconchar(hashtable_search(HASH,l->entry[0]),0);
        }
        bstrListDestroy(l);
        bdestroy (line);
    }
    fclose (f);

    // Remove NONE entries from all the other hashes

    void *itr;
    if (hashtable_count(NONE) > 0)
    {
        bstring k,v;

        itr = hashtable_iterator(NONE);
        do {
            k = hashtable_iterator_key(itr);
            v = hashtable_iterator_value(itr);

            if (biseq(k,&_AAAA))
            {
                HASH=AAAA;
            }
            else if (biseq(k,&_A))
            {
                HASH=A;
            }
            else if (biseq(k,&_SPF))
            {
                HASH=SPF;
            }
            else if (biseq(k,&_NONE))
            {
                HASH=NONE;
            }
            else if (biseq(k,&_TXT))
            {
                HASH=TXT;
            }
            else if (biseq(k,&_PTR))
            {
                HASH=PTR;
            }
            else if (biseq(k,&_MX))
            {
                HASH=MX;
            }
            while (hashtable_search(HASH,v))
            {
                bstring ss=hashtable_search(HASH,v);
                bdestroy(ss);
                hashtable_remove(HASH,v);
            }
        } while (hashtable_iterator_advance(itr));
        free(itr);
    }
    //dump_hash(SPF);
    return;
}

struct bstrList *spf_query_dns_a(spf_query *q,bstring domainname, bstring __A)
{
    bstring tmp;

    //Get a list of IP addresses for a domainname.
    if (!__A)
    {
        __A=bfromcstr("A");
    }
    if (domainname && domainname->slen)
    {
        bstring tmp;
        if (biseq(__A,&_A))
        {
            tmp=hashtable_search(A,domainname);
            if (!tmp && hashtable_search(TO,domainname))
            {
                throw (TEMP_ERROR,bfromcstr("DNS Dummy A TIMEOUT"));
            }
        }
        else if (biseq(__A,&_AAAA))
        {
            tmp=hashtable_search(AAAA,domainname);
            if (!tmp && hashtable_search(TO,domainname))
            {
                throw (TEMP_ERROR,bfromcstr("DNS Dummy AAAA TIMEOUT"));
            }
        }
        struct bstrList *l=bsplit(tmp,'|');
        if (q->strict && l && l->qty ==0)
        {
            throw ( AMBIGUITY_WARNING,
                   bformat ("No %s records found for %s",
                            __A->data,
                            domainname->data));
        }
        return l;
    }
    return 0;

}

struct bstrList *spf_query_dns_txt(spf_query *q,bstring domainname)
{
    if (domainname && domainname->slen)
    {
        bstring res=hashtable_search(TXT,domainname);
        if (!res && hashtable_search(TO,domainname))
        {
            throw (TEMP_ERROR,bfromcstr("DNS Dummy TXT TIMEOUT"));
        }
        struct bstrList *l=bsplit(res,'|');
        return l;
    }
    return 0;
}

struct bstrList *spf_query_dns_99(spf_query *q,bstring domainname)
{
    if (domainname && domainname->slen)
    {
        btolower(domainname);
        bstring res=hashtable_search(SPF,domainname);
        if (!res && hashtable_search(TO,domainname))
        {
            throw (TEMP_ERROR,bfromcstr("DNS Dummy 99 TIMEOUT"));
        }
        struct bstrList *l=bsplit(res,'|');
        return l;
    }
    return 0;
}

struct bstrList *spf_query_dns_mx(spf_query *q,bstring domainname)
{
    if (domainname && domainname->slen)
    {
        bstring res;
        struct bstrList *l=0;

        res=hashtable_search(MX,domainname);
        if (!res && hashtable_search(TO,domainname))
        {
            throw (TEMP_ERROR,bfromcstr("DNS Dummy MX TIMEOUT"));
        }
        if (res)
        {
            l=bsplit(res,'|');
        }

        int max;
        if (q->strict)
        {
            max=MAX_MX;
            if (q->strict>1)
            {
                if (l->qty> MAX_MX)
                {
                    throw (AMBIGUITY_WARNING,
                           bformat ("More than %d MX records returned %s",
                                    domainname->data));
                }
                if (l->qty == 0)
                {
                    throw (AMBIGUITY_WARNING,
                           bformat ("no MX records found for mx mechanism %s",
                                    domainname->data));
                }
            }
        }
        else
        {
            max = MAX_MX*4;
        }
        if (l)
        {
            if (max > l->qty)
                max= l->qty;

            bstring tmp=bfromcstr("");
            int i,j;
            for (i=0;i<max;i++)
            {
                struct bstrList *l2=spf_query_dns_a(q,l->entry[i],q->A);
                if (!l2) //MX has no A record????
                {
                    continue;
                }
                for (j=0;j<l2->qty;j++)
                {
                    bconcat(tmp,l2->entry[j]);
                    bconchar(tmp,0);
                }
                bstrListDestroy(l2);
            }
            bstrListDestroy(l);
            l=bsplit(tmp,0);
            bdestroy(tmp);
            return l;
        }
    }
    return 0;
}

struct bstrList *spf_query_dns_ptr(spf_query *q,bstring ip)
{
    if (! validate_ipv4(ip->data))
    {
        // Normalize the format of the IPV6 address
        // in an idiotic way

        struct bstrList *l=bsplit(ip,':');
        int i=0;
        for (;i<l->qty;i++)
        {
            if (l->entry[i]->slen>0)
            {
                while (l->entry[i]->slen<4)
                {
                    bassignformat(l->entry[i],"0%s",l->entry[i]->data);
                }
            }
        }
        struct tagbstring joiner=bsStatic(":");
        bstring tt=bjoin(l,&joiner);
        bassign(ip,tt);
        bdestroy(tt);
        bstrListDestroy(l);

        struct tagbstring cc=bsStatic("::");
        struct tagbstring cc2=bsStatic(":0000::");
        struct tagbstring cc3=bsStatic(":");
        while (ip->slen<39)
        {
            bfindreplace (ip,&cc,&cc2,0);
        }
        bfindreplace (ip,&cc,&cc3,0);
    }

    bstring res=hashtable_search(PTR,ip);

    if (!res && hashtable_search(TO,ip))
    {
        throw (TEMP_ERROR,bfromcstr("DNS Dummy PTR TIMEOUT"));
    }
    if (res)
    {
        struct bstrList *l=bsplit(res,'|');
        return l;
    }
    return 0;
}



